package view;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashSet;
import java.util.List;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import model.viewModels.CustomEventSubscriber;

/**
 * Creates a custom table, consisting of JLabels as table fields, wrapped in a JPanel and a JScrollPane.
 * 
 * @author kramer.david.87@gmail.com
 * @version 1.01
 */
@SuppressWarnings( "serial" )
public class CustomTable extends JScrollPane {

    /**
     * background color for table header
     */
    private static Color headerColor = new Color( 191, 212, 227 );
    /**
     * background color for every second table value
     */
    private static Color valueColor = new Color( 228, 232, 235 );
    /**
     * background color for table value
     */
    private static Color valueStandard = new Color( 242, 242, 242 );
    /**
     * background color for clicked table value
     */
    private static Color valueClicked = new Color( 255, 166, 170 );
    /**
     * the GridBagConstraint used for the header and value labels
     */
    private static GridBagConstraints customGridBag;

    /**
     * if table line is selected, this value holds the line index (starting with 0), otherwise its -1 (nothing selected)
     */
    private int selected = -1;

    /**
     * JPanel to place in the table content and adjust table position
     */
    private JPanel panel;

    /**
     * JPanel that will contain table header
     */
    private JPanel topPanel;

    /**
     * String field with table content
     */
    private JLabel[][] values;

    /**
     * String field with table header
     */
    private JLabel[] header;

    /**
     * list of subscribed custom event listeners
     */
    private HashSet<CustomEventSubscriber> subscribers;

    /**
     * static initialization of the GridBagConstraint
     */
    static
    {
	customGridBag = new GridBagConstraints();
	customGridBag.insets = new Insets( 0, 0, 1, 1 );
	customGridBag.weightx = 0.5;
	customGridBag.gridwidth = 1;
	customGridBag.gridheight = 1;
	customGridBag.fill = GridBagConstraints.HORIZONTAL;
    }

    /**
     * Constructor
     * 
     * @param header
     *            String values for table header
     * @param values
     *            String values for table content, may be null
     */
    public CustomTable( String[] header, String[][] values )
    {
	// remove JScrollPane border
	this.setBorder( null );

	// Panel + GridBagLayout
	this.panel = new JPanel();
	this.panel.setLayout( new GridBagLayout() );

	// create header labels
	JLabel l;
	int hL = header.length;
	this.header = new JLabel[hL];
	for ( int i = 0; i < hL; i++ )
	{
	    l = new JLabel( header[i] );
	    l.setHorizontalAlignment( JLabel.CENTER );
	    l.setBackground( headerColor );
	    l.setOpaque( true );
	    this.header[i] = l;
	}

	// fill table header
	for ( int i = 0; i < hL; i++ )
	{
	    customGridBag.gridx = i;
	    customGridBag.gridy = 0;
	    customGridBag.ipady = 10;
	    this.panel.add( this.header[i], customGridBag );
	}

	// create, set and add the value labels
	createSetAddValueLabels( values );

	// initialize subscribers list
	this.subscribers = new HashSet<CustomEventSubscriber>();

	// prepare the component structure
	this.topPanel = new JPanel( new BorderLayout() );
	this.topPanel.add( this.panel, BorderLayout.NORTH );
	this.getViewport().add( this.topPanel );
    }

    /**
     * sets the values of this CustomTable
     * 
     * @param values
     *            the values
     */
    private void createSetAddValueLabels( String[][] values )
    {
	// create value labels
	if ( values == null )
	{
	    this.values = new JLabel[0][0];
	}
	else if ( values.length == 0 )
	{
	    this.values = new JLabel[0][0];
	}
	else
	{
	    this.values = new JLabel[values.length][values[0].length];
	}

	JLabel l;
	for ( int i = 0; i < this.values.length; i++ )
	{
	    for ( int j = 0; j < this.values[0].length; j++ )
	    {
		l = new JLabel( values[i][j] );
		l.setOpaque( true );
		l.setHorizontalAlignment( JLabel.CENTER );
		l.addMouseListener( new MouseAdapter() {

		    @Override
		    public void mousePressed( MouseEvent e )
		    {
			super.mousePressed( e );
			colorContentLine( (JLabel)e.getComponent() );
			notifySubscribers();
		    }
		} );
		this.values[i][j] = l;
	    }
	}
	l = null;

	// initial background coloring
	setDefaultBackground();

	// fill table values
	for ( int i = 0; i < this.values.length; i++ )
	{
	    for ( int j = 0; j < this.values[0].length; j++ )
	    {
		customGridBag.gridx = j;
		customGridBag.gridy = i + 1;
		customGridBag.ipady = 5;
		this.panel.add( this.values[i][j], customGridBag );
	    }
	}
    }

    /**
     * adjusts background color for table content to default settings
     */
    private void setDefaultBackground()
    {
	for ( int i = 0; i < this.values.length; i++ )
	{
	    for ( int j = 0; j < this.values[i].length; j++ )
	    {
		if ( i % 2 == 0 )
		{
		    this.values[i][j].setBackground( valueColor );
		}
		else
		{
		    this.values[i][j].setBackground( valueStandard );
		}
	    }
	}
    }

    /**
     * Resets the internal row selection to -1.
     */
    private void deselect()
    {
	this.selected = -1;
    }

    /**
     * Reset to default colors, then set background color for the clicked line
     * 
     * @param jlb
     *            the clicked on JLabel aka table value
     */
    private void colorContentLine( JLabel jlb )
    {
	this.setDefaultBackground();
	for ( int i = 0; i < this.values.length; i++ )
	{
	    for ( int j = 0; j < this.values[0].length; j++ )
	    {
		if ( jlb == this.values[i][j] )
		{
		    for ( int j2 = 0; j2 < this.values[0].length; j2++ )
		    {
			if ( this.getSelected() == i )
			{
			    this.setDefaultBackground();
			    this.deselect();
			    return;
			}
			else
			{
			    this.values[i][j2].setBackground( valueClicked );
			}

		    }
		    this.selected = i;
		    return;
		}
	    }
	}
    }

    /**
     * updates the values of this CustomTable
     * 
     * @param values
     *            the new values
     */
    public final void updateValues( String[][] values )
    {
	// store data of marked row
	String markedData = getSelectedValues();

	// remove old values
	for ( int i = 0; i < this.values.length; i++ )
	{
	    for ( int j = 0; j < this.values[0].length; j++ )
	    {
		this.panel.remove( this.values[i][j] );
	    }
	}

	// set new values
	this.createSetAddValueLabels( values );

	// if new data are empty or the marked row is now more in this table
	this.deselect();
	// if new data are not empty, check if marked row is still in this table
	for ( int i = 0; i < this.values.length; i++ )
	{
	    if ( getSelectedValuesById( i ).split( ";" )[0].equals( markedData.split( ";" )[0] ) )
	    {
		this.selected = i;
		break;
	    }
	}

	// recolor the selected row
	if ( this.selected != -1 )
	{
	    for ( int i = 0; i < this.values[0].length; i++ )
	    {
		this.values[this.selected][i].setBackground( valueClicked );
	    }
	}

	// invoke update methods of all subscribers
	notifySubscribers();

	// validate and repaint
	validate();
	repaint();
    }

    /**
     * marks the declined dares
     * 
     * @param declDareIndexes
     *            the indexes of the declined dares
     */
    public final void markDeclinedDares( List<Integer> declDareIndexes )
    {
	for ( int i = 0; i < this.values.length; i++ )
	{
	    if ( declDareIndexes.contains( i ) )
	    {
		for ( int j = 0; j < this.values[0].length; j++ )
		{
		    this.values.clone()[i][j].setForeground( Color.RED );
		}
	    }
	}
    }

    /**
     * returns true, when a table line is selected, otherwise false
     * 
     * @return boolean
     */
    public final boolean isSelected()
    {
	if ( this.selected < 0 ) { return false; }
	return true;
    }

    /**
     * returns the values from the selected table line, separated by semicolon
     * 
     * @return String values CSV-style
     */
    public final String getSelectedValues()
    {
	StringBuilder ret = new StringBuilder();
	if ( this.isSelected() )
	{
	    ret.append( getSelectedValuesById( this.selected ) );
	}
	return ret.toString();
    }

    /**
     * returns the values from a table line, separated by semicolon
     * 
     * @param valuesId
     *            the number of the table line
     * @return String values CSV-Style
     */
    private String getSelectedValuesById( int valuesId )
    {
	StringBuilder ret = new StringBuilder();

	for ( int i = 0; i < this.values[0].length - 1; i++ )
	{
	    ret.append( this.values[valuesId][i].getText() + ";" );
	}
	ret.append( this.values[valuesId][this.values[0].length - 1].getText() );

	return ret.toString();
    }

    /**
     * selects a row of this custom table
     * 
     * @param selected
     *            the row index
     */
    public final void setSelectedExternal( int selected )
    {
	if ( this.selected != selected )
	{
	    this.colorContentLine( this.values[selected][0] );
	}
	notifySubscribers();
    }

    /**
     * Adds a new subscriber that is to be notified.
     * 
     * @param sub
     *            Object implementing CustomEventSubscriber-Interface
     */
    public final void subscribe( CustomEventSubscriber sub )
    {
	this.subscribers.add( sub );
    }

    /**
     * Removes a subscriber from notify list.
     * 
     * @param sub
     *            Object implementing CustomEventSubscriber-Interface
     */
    public final void unsubscribe( CustomEventSubscriber sub )
    {
	this.subscribers.remove( sub );
    }

    /**
     * notifies every subscribed object that a click event occurred
     */
    private void notifySubscribers()
    {
	for ( CustomEventSubscriber sub : this.subscribers )
	{
	    sub.update( this );
	}
    }

    /**
     * getter of the selected row
     * 
     * @return the selected row
     */
    public final int getSelected()
    {
	return this.selected;
    }
}
